<!DOCTYPE html>
<html lang="en-US">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>第6章 Vue 动画 | 深入理解Vue.js实战</title>
    <meta name="generator" content="VuePress 1.9.7">
    
    <meta name="description" content="作者：被删">
    
    <link rel="preload" href="/vue-ebook/assets/css/0.styles.2728a2da.css" as="style"><link rel="preload" href="/vue-ebook/assets/js/app.a856ee15.js" as="script"><link rel="preload" href="/vue-ebook/assets/js/2.2a4d101b.js" as="script"><link rel="preload" href="/vue-ebook/assets/js/3.9083e877.js" as="script"><link rel="preload" href="/vue-ebook/assets/js/20.f187c6c2.js" as="script">
    <link rel="stylesheet" href="/vue-ebook/assets/css/0.styles.2728a2da.css">
  </head>
  <body>
    <div id="app" data-server-rendered="true"><div class="theme-container"><header class="navbar"><div class="sidebar-button"><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img" viewBox="0 0 448 512" class="icon"><path fill="currentColor" d="M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z"></path></svg></div> <a href="/vue-ebook/" class="home-link router-link-active"><!----> <span class="site-name">深入理解Vue.js实战</span></a> <div class="links"><div class="search-box"><input aria-label="Search" autocomplete="off" spellcheck="false" value=""> <!----></div> <nav class="nav-links can-hide"><div class="nav-item"><a href="/vue-ebook/" class="nav-link">概述</a></div><div class="nav-item"><a href="/vue-ebook/vue-ebook/" class="nav-link router-link-active">内容</a></div> <a href="https://github.com/godbasin/vue-ebook" target="_blank" rel="noopener noreferrer" class="repo-link">
    Github
    <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></nav></div></header> <div class="sidebar-mask"></div> <aside class="sidebar"><nav class="nav-links"><div class="nav-item"><a href="/vue-ebook/" class="nav-link">概述</a></div><div class="nav-item"><a href="/vue-ebook/vue-ebook/" class="nav-link router-link-active">内容</a></div> <a href="https://github.com/godbasin/vue-ebook" target="_blank" rel="noopener noreferrer" class="repo-link">
    Github
    <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></nav>  <ul class="sidebar-links"><li><section class="sidebar-group depth-0" style="padding-top:;"><!----> <p class="sidebar-heading"><span>前言</span> <!----></p> <ul class="sidebar-links sidebar-group-items"><li><a href="/vue-ebook/vue-ebook/0.html" class="sidebar-link">前端框架的出现</a></li></ul></section></li><li><section class="sidebar-group depth-0" style="padding-top:10px;"><div class="kitty-main" data-v-2b653b36><span class="stand" data-v-2b653b36></span> <div class="cat" data-v-2b653b36><div class="body" data-v-2b653b36></div> <div class="head" data-v-2b653b36><div class="ear" data-v-2b653b36></div> <div class="ear" data-v-2b653b36></div></div> <div class="face" data-v-2b653b36><div class="nose" data-v-2b653b36></div> <div class="whisker-container" data-v-2b653b36><div class="whisker" data-v-2b653b36></div> <div class="whisker" data-v-2b653b36></div></div> <div class="whisker-container" data-v-2b653b36><div class="whisker" data-v-2b653b36></div> <div class="whisker" data-v-2b653b36></div></div></div> <div class="tail-container" data-v-2b653b36><div class="tail" data-v-2b653b36><div class="tail" data-v-2b653b36><div class="tail" data-v-2b653b36><div class="tail" data-v-2b653b36><div class="tail" data-v-2b653b36><div class="tail" data-v-2b653b36><div class="tail" data-v-2b653b36></div></div></div></div></div></div></div></div></div></div> <p class="sidebar-heading open"><span>第一部分 Vue快速入门</span> <!----></p> <ul class="sidebar-links sidebar-group-items"><li><a href="/vue-ebook/vue-ebook/1.html" class="sidebar-link">第1章 Vue 框架介绍</a></li><li><a href="/vue-ebook/vue-ebook/2.html" class="sidebar-link">第2章 Vue 环境快速搭建</a></li><li><a href="/vue-ebook/vue-ebook/3.html" class="sidebar-link">第3章 Vue 基础介绍</a></li><li><a href="/vue-ebook/vue-ebook/4.html" class="sidebar-link">第4章 Vue 组件的使用</a></li><li><a href="/vue-ebook/vue-ebook/5.html" class="sidebar-link">第5章 常用指令和自定义指令</a></li><li><a href="/vue-ebook/vue-ebook/6.html" aria-current="page" class="active sidebar-link">第6章 Vue 动画</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a href="/vue-ebook/vue-ebook/6.html#_6-1-transition-组件" class="sidebar-link">6.1 transition 组件</a></li><li class="sidebar-sub-header"><a href="/vue-ebook/vue-ebook/6.html#_6-2-css-过渡与动画" class="sidebar-link">6.2 CSS 过渡与动画</a></li><li class="sidebar-sub-header"><a href="/vue-ebook/vue-ebook/6.html#_6-3-javascript-钩子" class="sidebar-link">6.3 Javascript 钩子</a></li><li class="sidebar-sub-header"><a href="/vue-ebook/vue-ebook/6.html#_6-4-多元素-组件过渡" class="sidebar-link">6.4 多元素/组件过渡</a></li><li class="sidebar-sub-header"><a href="/vue-ebook/vue-ebook/6.html#_6-5-flip-与列表过渡" class="sidebar-link">6.5 FLIP 与列表过渡</a></li></ul></li><li><a href="/vue-ebook/vue-ebook/7.html" class="sidebar-link">第7章 Vue Router 路由搭建应用</a></li><li><a href="/vue-ebook/vue-ebook/8.html" class="sidebar-link">第8章 实战：Todo List 从组件到应用</a></li></ul></section></li><li><section class="sidebar-group depth-0" style="padding-top:;"><!----> <p class="sidebar-heading"><span>第二部分 Vue的正确使用方式</span> <!----></p> <ul class="sidebar-links sidebar-group-items"><li><a href="/vue-ebook/vue-ebook/9.html" class="sidebar-link">第9章 思维转变与大型项目管理</a></li><li><a href="/vue-ebook/vue-ebook/10.html" class="sidebar-link">第10章 如何正确地进行抽象</a></li><li><a href="/vue-ebook/vue-ebook/11.html" class="sidebar-link">第11章 全局数据管理与 Vuex</a></li><li><a href="/vue-ebook/vue-ebook/12.html" class="sidebar-link">第12章 实战：三天开发一个管理端</a></li><li><a href="/vue-ebook/vue-ebook/13.html" class="sidebar-link">第13章 实战：表单配置化实现</a></li><li><a href="/vue-ebook/vue-ebook/14.html" class="sidebar-link">第14章 实战：使用 Webpack 或 Vue CLI 搭建多页应用</a></li><li><a href="/vue-ebook/vue-ebook/15.html" class="sidebar-link">第15章 Vue 周边拓展</a></li><li><a href="/vue-ebook/vue-ebook/16.html" class="sidebar-link">第16章 关于 Vue 3.0</a></li></ul></section></li><li><section class="sidebar-group depth-0" style="padding-top:;"><!----> <p class="sidebar-heading"><span>后记</span> <!----></p> <ul class="sidebar-links sidebar-group-items"><li><a href="/vue-ebook/vue-ebook/99.html" class="sidebar-link">关于框架选型</a></li></ul></section></li></ul> </aside> <main class="page"> <div class="theme-default-content content__default"><h1 id="第6章-vue-动画"><a href="#第6章-vue-动画" class="header-anchor">#</a> 第6章 Vue 动画</h1> <blockquote><p>本章节相关代码存放在<a href="https://github.com/godbasin/vue-ebook/tree/vue-sourcecode/6" target="_blank" rel="noopener noreferrer">Github<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a>中。</p></blockquote> <p>CSS3 的出现，使得前端动画直接上了一个层次。我们可以抛掉落后的<code>.animate()</code>方法，通过 transition、animation 等样式属性，只需要配置起点终点、时间、加速度曲线，甚至还可以配置关键帧、循环播放等，就可以实现超级高大上的动效，同时还不用小心翼翼地避开性能问题。</p> <p>而 Vue 则是再一次优化了动画的使用，提供了多组件过渡、多元素过渡、CSS 动画、Javascript 动画等各种方式，开发者可以很方便地实现一些常用的列表过渡、页面切换等动画效果。不知道大家平时用的多不多，但如果你也有仔细研究的话，会发现还是挺有意思的。</p> <h2 id="_6-1-transition-组件"><a href="#_6-1-transition-组件" class="header-anchor">#</a> 6.1 transition 组件</h2> <p><code>&lt;transition&gt;</code>元素作为单个元素/组件的过渡效果，它只会把过渡效果应用到其包裹的内容上，而不会额外渲染 DOM 元素，也不会出现在检测过的组件层级中。</p> <h3 id="_6-1-1-vue-过渡常用方式"><a href="#_6-1-1-vue-过渡常用方式" class="header-anchor">#</a> 6.1.1 Vue 过渡常用方式</h3> <p>Vue 过渡主要分为两类：<strong>CSS 动画</strong>和<strong>Javascript 动画</strong>。</p> <p>根据官方描述，Vue 在插入、更新或者移除 DOM 时，提供多种不同方式的应用过渡效果，举例如下：</p> <ul><li>在 CSS 过渡和动画中自动应用 class（CSS 动画）</li> <li>可以配合使用第三方 CSS 动画库，如 Animate.css（CSS 动画）</li> <li>在过渡钩子函数中使用 JavaScript 直接操作 DOM（Javascript 动画）</li> <li>可以配合使用第三方 JavaScript 动画库，如 Velocity.js（Javascript 动画）</li></ul> <p>这些我们不一定全部会用到，常用的可能包括列表过渡、路由过渡、组件过渡一些方式。我们先来看看动画过渡的一些方法和原理。</p> <h3 id="_6-1-2-动画过渡的方法和原理"><a href="#_6-1-2-动画过渡的方法和原理" class="header-anchor">#</a> 6.1.2 动画过渡的方法和原理</h3> <p>以前我写动画的日子里，通常是使用 jQuery，各种计算然后调用<code>.animate()</code>等。后面也接触了 CSS3，transform 到 animation，省去了不少的力气，CSS 的动画能力简直要上天了。而关于 requestAnimationFrame，如果说没怎么写过动画的，或许几乎没听过，但如果你做动画，大概都会需要知道这么一个方法。</p> <h4 id="强大的-requestanimationframe"><a href="#强大的-requestanimationframe" class="header-anchor">#</a> 强大的 requestAnimationFrame</h4> <p>一般来说，大家常用的方式主要是来解决传说中浏览器刷新频率的问题。我们来看看 MDN 上是怎么介绍的：<code>window.requestAnimationFrame()</code>方法告诉浏览器您希望执行动画，并请求浏览器在下一次重绘之前调用指定的函数来更新动画。该方法使用一个回调函数作为参数，这个回调函数会在浏览器重绘之前调用。</p> <p>解决什么问题呢？例如常用浏览器的绘制间隔是 16.7ms，如果开发者强制使用 10ms 的间隔进行<code>setInterval()</code>或者<code>setTimeout()</code>来绘制动画，就会出现掉帧的情况，动画会变得卡顿而不流畅。</p> <p>使用 requestAnimationFrame，就是跟着浏览器的绘制频率走，如果浏览设备绘制间隔是 16.7ms, 就 16.7ms 绘制。这样就不会存在过度绘制的问题，动画不会掉帧。requestAnimationFrame 做的事情很简单，浏览器页面每次要重绘，就会通知 requestAnimationFrame，然后把需要绘制的内容一起带上进行绘制，这是资源非常高效的一种利用方式。</p> <p><strong>使用方式</strong></p> <div class="language-js extra-class"><pre class="language-js"><code>window<span class="token punctuation">.</span><span class="token function">requestAnimationFrame</span><span class="token punctuation">(</span>callback<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre></div><p><strong>callback</strong><br>
一个在每次需要重新绘制动画时调用的包含指定函数的参数。这个回调函数有一个传参，DOMHighResTimeStamp，指示从触发 requestAnimationFrame 回调到现在（重新渲染页面内容之前）的时间。</p> <p><strong>返回值</strong><br>
一个 long 整数，请求 ID，也是回调列表中唯一的标识。可以传此值到<code>window.cancelAnimationFrame()</code>以取消回调函数。</p> <p>总之，我们先来看看 Vue 里面的相关代码：</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token comment">// binding to window is necessary to make hot reload work in IE in strict mode</span>
<span class="token comment">// 此处在不兼容时使用setTimeout进行向下兼容</span>
<span class="token keyword">const</span> raf <span class="token operator">=</span>
  inBrowser <span class="token operator">&amp;&amp;</span> window<span class="token punctuation">.</span>requestAnimationFrame
    <span class="token operator">?</span> window<span class="token punctuation">.</span><span class="token function">requestAnimationFrame</span><span class="token punctuation">.</span><span class="token function">bind</span><span class="token punctuation">(</span>window<span class="token punctuation">)</span>
    <span class="token operator">:</span> setTimeout<span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">nextFrame</span><span class="token punctuation">(</span><span class="token parameter"><span class="token literal-property property">fn</span><span class="token operator">:</span> Function</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token function">raf</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token function">raf</span><span class="token punctuation">(</span>fn<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre></div><p><code>nextFrame</code>就是 Vue 中用来 CSS 动画过渡的方法，可以在下一次重绘之前进行一些处理。后面讲 CSS 动画过渡的时候我们会再详细点分析。</p> <h4 id="关键的-transitionend-和-animationend"><a href="#关键的-transitionend-和-animationend" class="header-anchor">#</a> 关键的 transitionend 和 animationend</h4> <p>这两个分别是 CSS 动画中的结束事件，transitionend 是 CSStransition（如 transform/scale 等）结束的触发事件，animationend 则是 CSS 动画（keyframe）结束的触发事件。</p> <p><strong>transitionend</strong><br>
transitionend 事件会在 CSS transition 结束后触发。以下两种情况不会触发：<br>
(1) 当 transition 完成前移除 transition 时，比如移除 css 的 transition-property 属性。<br>
(2) 在 transition 完成前设置 display 为&quot;none&quot;。</p> <p>使用方式如下：</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token comment">// 在指定的元素上监听transitionend事件</span>
element<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">&quot;transitionend&quot;</span><span class="token punctuation">,</span> callback<span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre></div><p><strong>animationend</strong><br>
animationend 事件会在一个 CSS 动画完成时触发。与 transitionend 相似，animationend 的触发不包括完成前就已终止的情况，例如元素变得不可见或者动画从元素中移除。</p> <p>使用方式如下：</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token comment">// 在指定的元素上监听animationend事件</span>
element<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">&quot;animationend&quot;</span><span class="token punctuation">,</span> callback<span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre></div><p>Vue 的 CSS 过渡中会使用到这两个事件，后续会讲到。相比 CSS transition，CSS 动画还有相关的事件，包括：</p> <ul><li><strong>animationstart</strong>: animationstart 事件会在 CSS 动画开始时触发。如果有 animation-delay 延时，事件会在延迟时效过后立即触发</li> <li><strong>animationiteration</strong>: 循环动画中，在每次循环结束时触发</li> <li><strong>animationcancel</strong>: animationcancel 事件会在 CSS 异常终止时触发（即在未触发 animationend 事件的情况下停止运行）</li></ul> <h3 id="_6-1-3-transition-组件"><a href="#_6-1-3-transition-组件" class="header-anchor">#</a> 6.1.3 transition 组件</h3> <p>Vue 提供了 transition 的封装组件，可以给元素和组件添加 entering/leaving 过渡。</p> <h4 id="transition-的使用"><a href="#transition-的使用" class="header-anchor">#</a> transition 的使用</h4> <p>我们看看有哪些情况下可以使用 transition：</p> <ul><li>条件渲染 (使用<code>v-if</code>)</li> <li>条件展示 (使用<code>v-show</code>)</li> <li>动态组件</li> <li>组件根节点</li></ul> <p>transition 的封装组件，主要是结合组件生命周期，在一些特殊逻辑（如<code>v-if</code>、<code>v-show</code>、<code>v-for</code>）里增加钩子，来触发动画的实现。我们知道，Vue 里面实现动画过渡有 CSS 和 Javascript 两种，而两种又是可以结合的，当然这是通过预埋的钩子以及上面讲到的 CSS 事件的方式来实现的。至于具体的实现，后面分篇来讲一下，这里就不过多讲述了。</p> <h4 id="来个-demo"><a href="#来个-demo" class="header-anchor">#</a> 来个 demo</h4> <p>我们直接来看看一个特别简单的例子：</p> <div class="language-html extra-class"><pre class="language-html"><code><span class="token comment">&lt;!-- transition的使用 --&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>demo<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name"><span class="token namespace">v-on:</span>click</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>show = !show<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>
    Toggle
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>transition</span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>fade<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">v-if</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>show<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>hello<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>transition</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
</code></pre></div><div class="language-css extra-class"><pre class="language-css"><code><span class="token comment">/* 简单的css transition实现动画 */</span>
<span class="token selector">.fade-enter-active,
.fade-leave-active</span> <span class="token punctuation">{</span>
  <span class="token property">transition</span><span class="token punctuation">:</span> opacity 0.5s<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
.fade-enter<span class="token punctuation">,</span> .fade-leave-to <span class="token comment">/* .fade-leave-active below version 2.1.8 */</span> <span class="token punctuation">{</span>
  <span class="token property">opacity</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre></div><p>这里是使用的 CSS transition 方式来实现动画过渡的，我们看看在切换<code>v-if</code>的时候都发生了什么:<br>
(1) <code>v-if</code>绑定值的变更，导致插入或删除包含在 transition 组件中的元素。<br>
(2) 自动嗅探目标元素是否应用了 CSS 过渡或动画。这里的确使用 CSS 过渡，于是会在元素添加时添加 CSS 类名，并判断动画加载完之后删除 CSS 类名。<br>
(3) 如果过渡组件提供了 JavaScript 钩子函数，这些钩子函数将在恰当的时机被调用。（这里由于没有使用到，故不会执行）<br>
(4) 如果没有找到 JavaScript 钩子并且也没有检测到 CSS 过渡/动画，DOM 操作 (插入/删除) 在下一帧中立即执行。（当然在我们的例子中，这一步也不会生效）</p> <h2 id="_6-2-css-过渡与动画"><a href="#_6-2-css-过渡与动画" class="header-anchor">#</a> 6.2 CSS 过渡与动画</h2> <p>下面我们来认识下 CSS 过渡。</p> <h3 id="_6-2-1-css-transition"><a href="#_6-2-1-css-transition" class="header-anchor">#</a> 6.2.1 CSS transition</h3> <p>CSS transitions 提供了一种在更改 CSS 属性时控制动画速度的方法。 其可以让属性变化成为一个持续一段时间的过程，而不是立即生效的。比如，将一个元素的颜色从白色改为黑色，通常这个改变是立即生效的，使用 CSS transitions 后该元素的颜色将逐渐从白色变为黑色，按照一定的曲线速率变化。这个过程可以自定义。</p> <p>通常将两个状态之间的过渡称为隐式过渡（implicit transitions），因为开始与结束之间的状态由浏览器决定。</p> <h4 id="transition-使用"><a href="#transition-使用" class="header-anchor">#</a> transition 使用</h4> <p>CSS transitions 可以决定哪些属性发生动画效果 (明确地列出这些属性)，何时开始 (设置 delay），持续多久 (设置 duration) 以及如何动画 (定义 timing funtion，比如匀速地或先快后慢)。</p> <p>CSS 过渡由简写属性 transition 定义是最好的方式：</p> <div class="language-css extra-class"><pre class="language-css"><code><span class="token selector">div</span> <span class="token punctuation">{</span>
  <span class="token property">transition</span><span class="token punctuation">:</span> &lt;property&gt; &lt;duration&gt; &lt;timing-function&gt; &lt;delay&gt;<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre></div><p>表 6-1 transition 属性说明</p> <table><thead><tr><th>属性名</th> <th>说明</th></tr></thead> <tbody><tr><td>property</td> <td>指定哪个或哪些 CSS 属性用于过渡。只有指定的属性才会在过渡中发生动画，其它属性仍如通常那样瞬间变化。all 则为全部属性</td></tr> <tr><td>duration</td> <td>指定过渡的时长。或者为所有属性指定一个值，或者指定多个值，为每个属性指定不同的时长</td></tr> <tr><td>timing-function</td> <td>指定一个函数，定义属性值怎么变化。常用如 linear、ease</td></tr> <tr><td>delay</td> <td>指定延迟，即属性开始变化时与过渡开始发生时之间的时长</td></tr></tbody></table> <p>下面是简写和不简写的例子：</p> <div class="language-css extra-class"><pre class="language-css"><code><span class="token selector">.short-for-transition</span> <span class="token punctuation">{</span>
  <span class="token property">transition</span><span class="token punctuation">:</span> margin-left 4s ease 2s<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token selector">.transition-detail</span> <span class="token punctuation">{</span>
  <span class="token property">transition-property</span><span class="token punctuation">:</span> margin-left<span class="token punctuation">;</span>
  <span class="token property">transition-duration</span><span class="token punctuation">:</span> 4s<span class="token punctuation">;</span>
  <span class="token property">transition-timing-function</span><span class="token punctuation">:</span> ease<span class="token punctuation">;</span>
  <span class="token property">transition-delay</span><span class="token punctuation">:</span> 2s<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre></div><h3 id="_6-2-2-vue-与-css-transition"><a href="#_6-2-2-vue-与-css-transition" class="header-anchor">#</a> 6.2.2 Vue 与 CSS transition</h3> <p>上面我们已经大概讲了 CSS transition 的使用方式，这里与 Vue 的结合也变得很简单，我们翻回刚刚用到的这个例子：</p> <div class="language-html extra-class"><pre class="language-html"><code><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>template</span><span class="token punctuation">&gt;</span></span>
  <span class="token comment">&lt;!-- transition的使用 --&gt;</span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>demo<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name"><span class="token namespace">v-on:</span>click</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>show = !show<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>
      Toggle
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>transition</span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>fade<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">v-if</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>show<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>hello<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>transition</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>template</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">&gt;</span></span><span class="token script"><span class="token language-javascript">
  <span class="token keyword">new</span> <span class="token class-name">Vue</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
    <span class="token literal-property property">el</span><span class="token operator">:</span> <span class="token string">&quot;#demo&quot;</span><span class="token punctuation">,</span>
    <span class="token literal-property property">data</span><span class="token operator">:</span> <span class="token punctuation">{</span>
      <span class="token literal-property property">show</span><span class="token operator">:</span> <span class="token boolean">true</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>style</span><span class="token punctuation">&gt;</span></span><span class="token style"><span class="token language-css">
  <span class="token comment">/* 简单的css transition实现动画 */</span>
  <span class="token selector">.fade-enter-active,
  .fade-leave-active</span> <span class="token punctuation">{</span>
    <span class="token property">transition</span><span class="token punctuation">:</span> opacity 0.5s<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  .fade-enter<span class="token punctuation">,</span> .fade-leave-to <span class="token comment">/* .fade-leave-active below version 2.1.8 */</span> <span class="token punctuation">{</span>
    <span class="token property">opacity</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>style</span><span class="token punctuation">&gt;</span></span>
</code></pre></div><blockquote><p><a href="https://vue-eboook-1255459943.cos.ap-chengdu.myqcloud.com/6/1-css-transition/1-css-transition.html" target="_blank" rel="noopener noreferrer">点击此处查看页面效果<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a> <a href="https://github.com/godbasin/vue-ebook/tree/vue-sourcecode/6/1-css-transition" target="_blank" rel="noopener noreferrer">点击此处查看源码<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></p></blockquote> <p>我们在 transition 上设置了 name 的值为 fade，然后效果是 Vue 匹配对应的 fade-status 的类名。下面我们来讲解下上述代码都做了什么。</p> <h4 id="过渡的类名"><a href="#过渡的类名" class="header-anchor">#</a> 过渡的类名</h4> <p>我们先来看看 Vue 里面提供了哪些类名：</p> <p>表 6-2 Vue 中过渡类名</p> <table><thead><tr><th>类名</th> <th>定义</th> <th>说明</th></tr></thead> <tbody><tr><td><code>v-enter</code></td> <td>定义进入过渡的开始状态</td> <td>在元素被插入时生效，在下一个帧移除</td></tr> <tr><td><code>v-enter-active</code></td> <td>定义过渡的状态</td> <td>在元素整个过渡过程中作用，在元素被插入时生效，在 transition/animation 完成之后移除<br>这个类可以被用来定义过渡的过程时间，延迟和曲线函数</td></tr> <tr><td><code>v-enter-to</code></td> <td>(2.1.8 版及以上)定义进入过渡的结束状态</td> <td>在元素被插入一帧后生效 (与此同时<code>v-enter</code>被删除)，在 transition/animation 完成之后移除</td></tr> <tr><td><code>v-leave</code></td> <td>定义离开过渡的开始状态</td> <td>在离开过渡被触发时生效，在下一个帧移除</td></tr> <tr><td><code>v-leave-active</code></td> <td>定义过渡的状态</td> <td>在元素整个过渡过程中作用，在离开过渡被触发后立即生效，在 transition/animation 完成之后移除<br>这个类可以被用来定义过渡的过程时间，延迟和曲线函数</td></tr> <tr><td><code>v-leave-to</code></td> <td>(2.1.8 版及以上)定义离开过渡的结束状态</td> <td>在离开过渡被触发一帧后生效 (与此同时<code>v-leave</code>被删除)，在 transition/animation 完成之后移除</td></tr></tbody></table> <p>文字很多，读起来有点费力气，是时候贴上官网的这个图了：
<img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/transition.png" alt="2.1.8 版本前"><br>
图 6-1 2.1.8 版本前 Vue 过渡类名说明</p> <p><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/transition2.1.8.png" alt="2.1.8 版及以上"><br>
图 6-2 2.1.8 版本及以上 Vue 过渡类名说明</p> <h4 id="实现逻辑"><a href="#实现逻辑" class="header-anchor">#</a> 实现逻辑</h4> <p>前面我们简单介绍了一些 transition 组件的实现原理相关的函数和事件，这里我们再详细分析下。根据前面内容，我们可以获得以下的信息：</p> <ul><li>可以使用 requestAnimationFrame 来请求浏览器在下一次重绘之前调用指定的函数来更新动画</li> <li>当 CSS transition 结束时，会触发 transitionend 事件</li></ul> <p>所以结合之前的猜想和 Vue 的源码，能大概得到这里的实现方式：<br>
(1) transition 组件关注子元素是否展示，包括使用<code>v-if</code>/<code>v-else</code>/<code>v-for</code>等指令绑定数据生成的元素。<br>
(2) 当元素状态变更（display -&gt; none 或 none -&gt; display）时，预埋的钩子检测是否应用了 CSS 过渡。<br>
(3) 若使用了 CSS 过渡，则分两种情况讨论。</p> <h4 id="进入动画"><a href="#进入动画" class="header-anchor">#</a> 进入动画</h4> <p>当新元素插入时，我们按照以下方式实现进入动画：<br>
(1) 当元素插入完成后（mounted），给元素添加对应 v-enter 和 v-enter-active 类名，此时元素开始动画过渡。<br>
(2) 同时通过 requestAnimationFrame 来指定下一帧绘制前，给元素添加 v-enter-to 类名，同时移除 v-enter 类名。<br>
(3) 设置动画结束时间，或者通过 transitionend 事件监听，在过渡结束后，移除 v-enter-to 和 v-enter-active 类名。</p> <h4 id="离开动画"><a href="#离开动画" class="header-anchor">#</a> 离开动画</h4> <p>当新元素被删除时，我们按照以下方式实现离开动画：<br>
(1) 当元素删除前（beforeDestroy），给元素添加对应 v-leave 和 v-leave-active 类名，此时元素开始动画过渡。<br>
(2) 同时通过 requestAnimationFrame 来指定下一帧绘制前，给元素添加 v-leave-to 类名，同时移除 v-leave 类名。<br>
(3) 设置动画结束时间，或者通过 transitionend 事件监听，在过渡结束后，移除 v-leave-to 和 v-leave-active 类名，同时执行元素删除操作。</p> <p>以上是整体实现方式，可见在我们使用 CSS 过渡的时候，最关键的是 v-enter-active 和 v-leave-active 两个样式。我们来看看 Vue 里面的部分代码：</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token comment">// start enter transition</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>expectsCSS<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token comment">// 添加v-enter和v-enter-active类名</span>
  <span class="token function">addTransitionClass</span><span class="token punctuation">(</span>el<span class="token punctuation">,</span> startClass<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token function">addTransitionClass</span><span class="token punctuation">(</span>el<span class="token punctuation">,</span> activeClass<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token comment">// nextFrame为下一帧前的执行函数</span>
  <span class="token comment">// 使用setTimeout向下兼容requestAnimationFrame</span>
  <span class="token function">nextFrame</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token comment">// 添加v-enter-to类名，移除v-enter类名</span>
    <span class="token function">addTransitionClass</span><span class="token punctuation">(</span>el<span class="token punctuation">,</span> toClass<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token function">removeTransitionClass</span><span class="token punctuation">(</span>el<span class="token punctuation">,</span> startClass<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>cb<span class="token punctuation">.</span>cancelled <span class="token operator">&amp;&amp;</span> <span class="token operator">!</span>userWantsControl<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">isValidDuration</span><span class="token punctuation">(</span>explicitEnterDuration<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token comment">// 若获取过渡的duration成功，则通过定时器来触发结束后逻辑</span>
        <span class="token function">setTimeout</span><span class="token punctuation">(</span>cb<span class="token punctuation">,</span> explicitEnterDuration<span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
        <span class="token comment">// 若获取过渡的duration失败，则通过监听transitionend事件来触发结束后逻辑</span>
        <span class="token function">whenTransitionEnds</span><span class="token punctuation">(</span>el<span class="token punctuation">,</span> type<span class="token punctuation">,</span> cb<span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre></div><p>当然，完整的 CSS 过渡、相关的钩子等会有更多的考虑和处理，这里只展示核心的实现逻辑部分。</p> <h3 id="_6-2-3-css-animation"><a href="#_6-2-3-css-animation" class="header-anchor">#</a> 6.2.3 CSS animation</h3> <p>CSS animations 使得可以将从一个 CSS 样式配置转换到另一个 CSS 样式配置。动画包括两个部分:描述动画的样式规则和用于指定动画开始、结束以及中间点样式的关键帧。</p> <p>创建动画序列，需要使用 animation 属性或其子属性，该属性允许配置动画时间、时长以及其他动画细节。和 transition 不一样的是，该属性不能配置动画的实际表现，动画的实际表现是由<code>@keyframes</code>规则实现。</p> <h4 id="使用-animation"><a href="#使用-animation" class="header-anchor">#</a> 使用 animation</h4> <p>我们来看看 animation 的使用方式，其实跟 transition 很相像的，子属性如表 6-3：</p> <p>表 6-3 animation 属性说明</p> <table><thead><tr><th>属性名</th> <th>说明</th></tr></thead> <tbody><tr><td>animation-delay</td> <td>设置延时，即从元素加载完成之后到动画序列开始执行的这段时间</td></tr> <tr><td>animation-direction</td> <td>设置动画在每次运行完后是反向运行还是重新回到开始位置重复运行</td></tr> <tr><td>animation-duration</td> <td>设置动画一个周期的时长</td></tr> <tr><td>animation-iteration-count</td> <td>设置动画重复次数， 可以指定 infinite 无限次重复动画</td></tr> <tr><td>animation-name</td> <td>指定由@keyframes 描述的关键帧名称</td></tr> <tr><td>animation-play-state</td> <td>允许暂停和恢复动画</td></tr> <tr><td>animation-timing-function</td> <td>设置动画速度， 即通过建立加速度曲线，设置动画在关键帧之间是如何变化</td></tr> <tr><td>animation-fill-mode</td> <td>指定动画执行前后如何为目标元素应用样式</td></tr></tbody></table> <p>同样的，CSS animation 属性也是一个简写属性形式:</p> <div class="language-css extra-class"><pre class="language-css"><code><span class="token comment">/* @keyframes duration | timing-function | delay |
   iteration-count | direction | fill-mode | play-state | name */</span>
<span class="token property">animation</span><span class="token punctuation">:</span> 3s ease-in 1s 2 reverse both paused slidein<span class="token punctuation">;</span>

<span class="token comment">/* @keyframes duration | timing-function | delay | name */</span>
<span class="token property">animation</span><span class="token punctuation">:</span> 3s linear 1s slidein<span class="token punctuation">;</span>

<span class="token comment">/* @keyframes duration | name */</span>
<span class="token property">animation</span><span class="token punctuation">:</span> 3s slidein<span class="token punctuation">;</span>
</code></pre></div><p>我们也可以用详细的子属性方式来写，这里就不详细描述啦。</p> <h4 id="关键帧动画"><a href="#关键帧动画" class="header-anchor">#</a> 关键帧动画</h4> <p>一旦完成动画的时间设置， 接下来就需要定义动画的表现。通过使用<code>@keyframes</code>建立两个或两个以上关键帧来实现。每一个关键帧都描述了动画元素在给定的时间点上应该如何渲染。</p> <p>因为动画的时间设置是通过 CSS 样式定义的，<strong>关键帧使用 percentage 来指定动画发生的时间点。0%表示动画的第一时刻，100%表示动画的最终时刻。<strong>因为这两个时间点十分重要，所以还有特殊的别名：<strong>from</strong>和</strong>to</strong>。当然，也可包含额外可选的关键帧，描述动画开始和结束之间的状态。来看个云在天上飘来飘去的 demo：</p> <div class="language-css extra-class"><pre class="language-css"><code><span class="token selector">.clouldmove</span> <span class="token punctuation">{</span>
  <span class="token property">position</span><span class="token punctuation">:</span> absolute<span class="token punctuation">;</span>
  <span class="token property">width</span><span class="token punctuation">:</span> 20%<span class="token punctuation">;</span>
  <span class="token property">margin</span><span class="token punctuation">:</span> 5%<span class="token punctuation">;</span>
  <span class="token property">float</span><span class="token punctuation">:</span> left<span class="token punctuation">;</span>
  <span class="token property">margin-left</span><span class="token punctuation">:</span> 70%<span class="token punctuation">;</span>
  <span class="token property">top</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span>
  <span class="token property">animation-name</span><span class="token punctuation">:</span> ClouldMove<span class="token punctuation">;</span>
  <span class="token property">animation-duration</span><span class="token punctuation">:</span> 8s<span class="token punctuation">;</span>
  <span class="token property">animation-timing-function</span><span class="token punctuation">:</span> linear<span class="token punctuation">;</span>
  <span class="token property">animation-iteration-count</span><span class="token punctuation">:</span> infinite<span class="token punctuation">;</span>
  <span class="token property">animation-direction</span><span class="token punctuation">:</span> alternate<span class="token punctuation">;</span>
  <span class="token property">animation-play-state</span><span class="token punctuation">:</span> running<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token atrule"><span class="token rule">@keyframes</span> ClouldMove</span> <span class="token punctuation">{</span>
  <span class="token selector">0%</span> <span class="token punctuation">{</span>
    <span class="token property">left</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token selector">25%</span> <span class="token punctuation">{</span>
    <span class="token property">left</span><span class="token punctuation">:</span> 5%<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token selector">50%</span> <span class="token punctuation">{</span>
    <span class="token property">left</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token selector">75%</span> <span class="token punctuation">{</span>
    <span class="token property">left</span><span class="token punctuation">:</span> 5%<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token selector">100%</span> <span class="token punctuation">{</span>
    <span class="token property">left</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre></div><p>还有很多的动画效果，包括轮回播放、来回运动等，这里不详细说明，大家感兴趣可以去 MDN 搜一下对应的内容。总之要记住，CSS animation 是个超级变态的动画效果实现。</p> <h4 id="animation-事件监听器"><a href="#animation-事件监听器" class="header-anchor">#</a> animation 事件监听器</h4> <p>上面我们讲到了 transitionend，animation 比它还多了些事件。</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token comment">// animationstart事件在动画一开始时就被触发</span>
e<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">&quot;animationstart&quot;</span><span class="token punctuation">,</span> listener<span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// 每个周期完成后（除了最后一个周期），会触发animationiteration事件</span>
e<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">&quot;animationend&quot;</span><span class="token punctuation">,</span> listener<span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// 最后一个周期完成后，不会触发animationiteration事件，而触发animationend事件</span>
e<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">&quot;animationiteration&quot;</span><span class="token punctuation">,</span> listener<span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre></div><p>对于 Vue 来说，不管是 transition 还是 animation，都是通过 CSS 和类名来实现动画的，所以基本逻辑原理相似，除去动画结束监听的事件不一样而已，所以这里也不过多讲解了。</p> <h2 id="_6-3-javascript-钩子"><a href="#_6-3-javascript-钩子" class="header-anchor">#</a> 6.3 Javascript 钩子</h2> <p>除了 CSS 过渡和动画，在 Vue 中还可以使用 Javascript 钩子的方式来实现动画效果。</p> <h3 id="_6-3-1-完整的钩子信息"><a href="#_6-3-1-完整的钩子信息" class="header-anchor">#</a> 6.3.1 完整的钩子信息</h3> <p>我们来看看 Vue 动画一共有多少的 Javascript 钩子：</p> <div class="language-html extra-class"><pre class="language-html"><code><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>transition</span>
  <span class="token attr-name"><span class="token namespace">v-on:</span>before-enter</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>beforeEnter<span class="token punctuation">&quot;</span></span>
  <span class="token attr-name"><span class="token namespace">v-on:</span>enter</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>enter<span class="token punctuation">&quot;</span></span>
  <span class="token attr-name"><span class="token namespace">v-on:</span>after-enter</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>afterEnter<span class="token punctuation">&quot;</span></span>
  <span class="token attr-name"><span class="token namespace">v-on:</span>enter-cancelled</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>enterCancelled<span class="token punctuation">&quot;</span></span>
  <span class="token attr-name"><span class="token namespace">v-on:</span>before-leave</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>beforeLeave<span class="token punctuation">&quot;</span></span>
  <span class="token attr-name"><span class="token namespace">v-on:</span>leave</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>leave<span class="token punctuation">&quot;</span></span>
  <span class="token attr-name"><span class="token namespace">v-on:</span>after-leave</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>afterLeave<span class="token punctuation">&quot;</span></span>
  <span class="token attr-name"><span class="token namespace">v-on:</span>leave-cancelled</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>leaveCancelled<span class="token punctuation">&quot;</span></span>
<span class="token punctuation">&gt;</span></span>
  <span class="token comment">&lt;!-- ... --&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>transition</span><span class="token punctuation">&gt;</span></span>
</code></pre></div><div class="language-js extra-class"><pre class="language-js"><code><span class="token comment">// ...</span>
<span class="token literal-property property">methods</span><span class="token operator">:</span> <span class="token punctuation">{</span>
  <span class="token comment">// --------</span>
  <span class="token comment">// 进入中</span>
  <span class="token comment">// --------</span>

  <span class="token function-variable function">beforeEnter</span><span class="token operator">:</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">el</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">// ...</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token comment">// 此回调函数是可选项的设置</span>
  <span class="token comment">// 与 CSS 结合时使用</span>
  <span class="token function-variable function">enter</span><span class="token operator">:</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">el<span class="token punctuation">,</span> done</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">// ...</span>
    <span class="token function">done</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token function-variable function">afterEnter</span><span class="token operator">:</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">el</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">// ...</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token function-variable function">enterCancelled</span><span class="token operator">:</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">el</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">// ...</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>

  <span class="token comment">// --------</span>
  <span class="token comment">// 离开时</span>
  <span class="token comment">// --------</span>

  <span class="token function-variable function">beforeLeave</span><span class="token operator">:</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">el</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">// ...</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token comment">// 此回调函数是可选项的设置</span>
  <span class="token comment">// 与 CSS 结合时使用</span>
  <span class="token function-variable function">leave</span><span class="token operator">:</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">el<span class="token punctuation">,</span> done</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">// ...</span>
    <span class="token function">done</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token function-variable function">afterLeave</span><span class="token operator">:</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">el</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">// ...</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token comment">// leaveCancelled 只用于 v-show 中</span>
  <span class="token function-variable function">leaveCancelled</span><span class="token operator">:</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">el</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">// ...</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre></div><p>这里对进入和离开的动画钩子说明也比较清晰了，我们来看看 demo。</p> <h4 id="使用-jquery-的例子"><a href="#使用-jquery-的例子" class="header-anchor">#</a> 使用 jQuery 的例子</h4> <p>官方提供一个使用 Velocity.js 的例子，我们这里就直接拿个最简单的 jQuery 动画来写。</p> <div class="language-html extra-class"><pre class="language-html"><code><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>app<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">@click</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>show = !show<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>
    Toggle
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>transition</span> <span class="token attr-name"><span class="token namespace">v-on:</span>enter</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>enter<span class="token punctuation">&quot;</span></span> <span class="token attr-name"><span class="token namespace">v-on:</span>leave</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>leave<span class="token punctuation">&quot;</span></span> <span class="token attr-name"><span class="token namespace">v-bind:</span>css</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>false<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">v-if</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>show<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>
      Demo
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>transition</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
</code></pre></div><div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">new</span> <span class="token class-name">Vue</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  <span class="token literal-property property">el</span><span class="token operator">:</span> <span class="token string">&quot;#app&quot;</span><span class="token punctuation">,</span>
  <span class="token literal-property property">data</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token literal-property property">show</span><span class="token operator">:</span> <span class="token boolean">false</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token literal-property property">methods</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token function-variable function">enter</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token parameter">el<span class="token punctuation">,</span> done</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
      <span class="token comment">// 元素已被插入 DOM</span>
      <span class="token comment">// 在动画结束后调用 done</span>
      <span class="token function">$</span><span class="token punctuation">(</span>el<span class="token punctuation">)</span>
        <span class="token punctuation">.</span><span class="token function">css</span><span class="token punctuation">(</span><span class="token string">&quot;opacity&quot;</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span>
        <span class="token punctuation">.</span><span class="token function">animate</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">opacity</span><span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token literal-property property">fontSize</span><span class="token operator">:</span> <span class="token string">&quot;100px&quot;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token number">200</span><span class="token punctuation">,</span> done<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token function-variable function">leave</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token parameter">el<span class="token punctuation">,</span> done</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
      <span class="token comment">// 与 enter 相同</span>
      <span class="token function">$</span><span class="token punctuation">(</span>el<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">animate</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">opacity</span><span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token literal-property property">fontSize</span><span class="token operator">:</span> <span class="token string">&quot;0px&quot;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token number">200</span><span class="token punctuation">,</span> done<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre></div><blockquote><p><a href="https://vue-eboook-1255459943.cos.ap-chengdu.myqcloud.com/6/2-jquery-transition/2-jquery-transition.html" target="_blank" rel="noopener noreferrer">点击此处查看页面效果<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a> <a href="https://github.com/godbasin/vue-ebook/tree/vue-sourcecode/6/2-jquery-transition" target="_blank" rel="noopener noreferrer">点击此处查看源码<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></p></blockquote> <h4 id="初始渲染"><a href="#初始渲染" class="header-anchor">#</a> 初始渲染</h4> <p>Vue 的动画提供了一个初始渲染的开关，指的是第一次展示（而不是第一次切换）的时候是否需要动画效果。具体可以看看上面的例子，当我们把 show 的默认值设置为 true 的时候，初次展示并不会加载进入动画。但是当我们在 transition 组件中添加 appear 属性时，则在初次展示时也会加载进入动画：</p> <div class="language-html extra-class"><pre class="language-html"><code><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>example-4<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">@click</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>show = !show<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>
    Toggle
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>transition</span> <span class="token attr-name">appear</span> <span class="token attr-name"><span class="token namespace">v-on:</span>enter</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>enter<span class="token punctuation">&quot;</span></span> <span class="token attr-name"><span class="token namespace">v-on:</span>leave</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>leave<span class="token punctuation">&quot;</span></span> <span class="token attr-name"><span class="token namespace">v-bind:</span>css</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>false<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">v-if</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>show<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>
      Demo
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>transition</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
</code></pre></div><p>当然，你也可以绑定 appear 专属的动画效果：</p> <div class="language-html extra-class"><pre class="language-html"><code><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>transition</span>
  <span class="token attr-name">appear</span>
  <span class="token attr-name"><span class="token namespace">v-on:</span>before-appear</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>customBeforeAppearHook<span class="token punctuation">&quot;</span></span>
  <span class="token attr-name"><span class="token namespace">v-on:</span>appear</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>customAppearHook<span class="token punctuation">&quot;</span></span>
  <span class="token attr-name"><span class="token namespace">v-on:</span>after-appear</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>customAfterAppearHook<span class="token punctuation">&quot;</span></span>
  <span class="token attr-name"><span class="token namespace">v-on:</span>appear-cancelled</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>customAppearCancelledHook<span class="token punctuation">&quot;</span></span>
<span class="token punctuation">&gt;</span></span>
  <span class="token comment">&lt;!-- ... --&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>transition</span><span class="token punctuation">&gt;</span></span>
</code></pre></div><h4 id="实现逻辑-2"><a href="#实现逻辑-2" class="header-anchor">#</a> 实现逻辑</h4> <p>前面我们详细描述了在使用 CSS 过渡和动画的时候，其中的具体实现原理。而 Javascript 钩子的实现逻辑其实很简单：<br>
(1) 检测组件是否使用了 Javascript 钩子。<br>
(2) 若使用了 Javascript 钩子，则这些钩子函数将在恰当的时机被调用。</p> <p>当然这些钩子分别穿插在各个地方，顺序的话也很明了。这里以离开动画为例子，我们来看看这段被我简化后的代码：</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">leave</span><span class="token punctuation">(</span><span class="token parameter"><span class="token literal-property property">vnode</span><span class="token operator">:</span> VNodeWithData<span class="token punctuation">,</span> <span class="token literal-property property">rm</span><span class="token operator">:</span> Function</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token comment">// el是被动画的元素</span>
  <span class="token keyword">const</span> <span class="token literal-property property">el</span><span class="token operator">:</span> any <span class="token operator">=</span> vnode<span class="token punctuation">.</span>elm<span class="token punctuation">;</span>

  <span class="token comment">// 当然如果进入动画还没结束，就取消吧~</span>
  <span class="token comment">// call enter callback now</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">isDef</span><span class="token punctuation">(</span>el<span class="token punctuation">.</span>_enterCb<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    el<span class="token punctuation">.</span>_enterCb<span class="token punctuation">.</span>cancelled <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
    el<span class="token punctuation">.</span><span class="token function">_enterCb</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token comment">// 嗯，这里获取该获取的东西，包括Javascript钩子和CSS类名</span>
  <span class="token keyword">const</span> data <span class="token operator">=</span> <span class="token function">resolveTransition</span><span class="token punctuation">(</span>vnode<span class="token punctuation">.</span>data<span class="token punctuation">.</span>transition<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">const</span> <span class="token punctuation">{</span>
    css<span class="token punctuation">,</span>
    type<span class="token punctuation">,</span>
    leaveClass<span class="token punctuation">,</span>
    leaveToClass<span class="token punctuation">,</span>
    leaveActiveClass<span class="token punctuation">,</span>
    beforeLeave<span class="token punctuation">,</span>
    leave<span class="token punctuation">,</span>
    afterLeave<span class="token punctuation">,</span>
    leaveCancelled<span class="token punctuation">,</span>
    delayLeave<span class="token punctuation">,</span>
    duration
  <span class="token punctuation">}</span> <span class="token operator">=</span> data<span class="token punctuation">;</span>

  <span class="token comment">// 这个cb比较重要啦，将</span>
  <span class="token keyword">const</span> cb <span class="token operator">=</span> <span class="token punctuation">(</span>el<span class="token punctuation">.</span>_leaveCb <span class="token operator">=</span> <span class="token function">once</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token comment">// 移除v-leave-to和v-leave-active类名</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>expectsCSS<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token function">removeTransitionClass</span><span class="token punctuation">(</span>el<span class="token punctuation">,</span> leaveToClass<span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token function">removeTransitionClass</span><span class="token punctuation">(</span>el<span class="token punctuation">,</span> leaveActiveClass<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">if</span> <span class="token punctuation">(</span>cb<span class="token punctuation">.</span>cancelled<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token comment">// 如果离开被取消，则移除v-leave类名</span>
      <span class="token keyword">if</span> <span class="token punctuation">(</span>expectsCSS<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token function">removeTransitionClass</span><span class="token punctuation">(</span>el<span class="token punctuation">,</span> leaveClass<span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span>
      <span class="token comment">// 看到了吧，这里埋下了leaveCancelled的Javascript钩子</span>
      leaveCancelled <span class="token operator">&amp;&amp;</span> <span class="token function">leaveCancelled</span><span class="token punctuation">(</span>el<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
      <span class="token function">rm</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token comment">// 这里埋下了afterLeave的Javascript钩子</span>
      afterLeave <span class="token operator">&amp;&amp;</span> <span class="token function">afterLeave</span><span class="token punctuation">(</span>el<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token comment">// 这个离开动画的函数只调用一次啦</span>
    el<span class="token punctuation">.</span>_leaveCb <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token comment">// 看看要不要延迟执行啦</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span>delayLeave<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token function">delayLeave</span><span class="token punctuation">(</span>performLeave<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
    <span class="token function">performLeave</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token comment">// 嗯，这个才是真正执行的函数</span>
  <span class="token keyword">function</span> <span class="token function">performLeave</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">// the delayed leave may have already been cancelled</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>cb<span class="token punctuation">.</span>cancelled<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">return</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token comment">// 看，这里埋下了beforeLeave的Javascript钩子</span>
    beforeLeave <span class="token operator">&amp;&amp;</span> <span class="token function">beforeLeave</span><span class="token punctuation">(</span>el<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">// 下面这是上一节讲到的逻辑，执行CSS过渡动画的</span>
    <span class="token comment">// cb是上面定义的回调啦，里面包括afterLeave钩子</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>expectsCSS<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token function">addTransitionClass</span><span class="token punctuation">(</span>el<span class="token punctuation">,</span> leaveClass<span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token function">addTransitionClass</span><span class="token punctuation">(</span>el<span class="token punctuation">,</span> leaveActiveClass<span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token function">nextFrame</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
        <span class="token function">addTransitionClass</span><span class="token punctuation">(</span>el<span class="token punctuation">,</span> leaveToClass<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token function">removeTransitionClass</span><span class="token punctuation">(</span>el<span class="token punctuation">,</span> leaveClass<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>cb<span class="token punctuation">.</span>cancelled <span class="token operator">&amp;&amp;</span> <span class="token operator">!</span>userWantsControl<span class="token punctuation">)</span> <span class="token punctuation">{</span>
          <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">isValidDuration</span><span class="token punctuation">(</span>explicitLeaveDuration<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
            <span class="token function">setTimeout</span><span class="token punctuation">(</span>cb<span class="token punctuation">,</span> explicitLeaveDuration<span class="token punctuation">)</span><span class="token punctuation">;</span>
          <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
            <span class="token function">whenTransitionEnds</span><span class="token punctuation">(</span>el<span class="token punctuation">,</span> type<span class="token punctuation">,</span> cb<span class="token punctuation">)</span><span class="token punctuation">;</span>
          <span class="token punctuation">}</span>
        <span class="token punctuation">}</span>
      <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token comment">// 看，这里还埋下了leave的Javascript钩子</span>
    <span class="token comment">// 和其他钩子不同的是，这里传进去cb了</span>
    leave <span class="token operator">&amp;&amp;</span> <span class="token function">leave</span><span class="token punctuation">(</span>el<span class="token punctuation">,</span> cb<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>expectsCSS <span class="token operator">&amp;&amp;</span> <span class="token operator">!</span>userWantsControl<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token function">cb</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre></div><p>逻辑大概是这样了，现在大家明白了<code>leave: function (el, done)</code>里的 done 是什么了。</p> <h2 id="_6-4-多元素-组件过渡"><a href="#_6-4-多元素-组件过渡" class="header-anchor">#</a> 6.4 多元素/组件过渡</h2> <p>对于列表、状态模块来说，常常会需要用到一些过渡动画，包括多元素、多组件之间的过渡方式。</p> <h3 id="_6-4-1-多元素过渡"><a href="#_6-4-1-多元素过渡" class="header-anchor">#</a> 6.4.1 多元素过渡</h3> <p>这里的多元素过渡，通常指的是 v-if 和 v-else 等指令绑定元素状态切换的动画效果。Vue 官网里面提到一个需要注意的地方：当有相同标签名的元素切换时，需要通过 key 特性设置唯一的值来标记以让 Vue 区分它们，否则 Vue 为了效率只会替换相同标签内部的内容。</p> <p>这个就跟<code>v-for</code>中绑定<code>key</code>是差不多原理的，总之是追踪元素，然后来进行操作或是优化更新算法。使用方式如下：</p> <div class="language-html extra-class"><pre class="language-html"><code><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>transition</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">v-if</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>docState === <span class="token punctuation">'</span>saved<span class="token punctuation">'</span><span class="token punctuation">&quot;</span></span> <span class="token attr-name">key</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>saved<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>
    Edit
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">v-if</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>docState === <span class="token punctuation">'</span>edited<span class="token punctuation">'</span><span class="token punctuation">&quot;</span></span> <span class="token attr-name">key</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>edited<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>
    Save
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">v-if</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>docState === <span class="token punctuation">'</span>editing<span class="token punctuation">'</span><span class="token punctuation">&quot;</span></span> <span class="token attr-name">key</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>editing<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>
    Cancel
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>transition</span><span class="token punctuation">&gt;</span></span>
</code></pre></div><h3 id="_6-4-2-多组件过渡"><a href="#_6-4-2-多组件过渡" class="header-anchor">#</a> 6.4.2 多组件过渡</h3> <p>多个组件的过渡要使用动态组件。什么是动态组件呢？通过使用保留的 component 元素，并对其 is 特性进行动态绑定，你可以在同一个挂载点动态切换多个组件：</p> <div class="language-html extra-class"><pre class="language-html"><code><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>component</span> <span class="token attr-name"><span class="token namespace">v-bind:</span>is</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>currentView<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>
  <span class="token comment">&lt;!-- 组件在 vm.currentview 变化时改变！ --&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>component</span><span class="token punctuation">&gt;</span></span>
</code></pre></div><div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">var</span> vm <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Vue</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  <span class="token literal-property property">el</span><span class="token operator">:</span> <span class="token string">&quot;#example&quot;</span><span class="token punctuation">,</span>
  <span class="token literal-property property">data</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token literal-property property">currentView</span><span class="token operator">:</span> <span class="token string">&quot;home&quot;</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token literal-property property">components</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token literal-property property">home</span><span class="token operator">:</span> <span class="token punctuation">{</span>
      <span class="token comment">/* ... */</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token literal-property property">posts</span><span class="token operator">:</span> <span class="token punctuation">{</span>
      <span class="token comment">/* ... */</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token literal-property property">archive</span><span class="token operator">:</span> <span class="token punctuation">{</span>
      <span class="token comment">/* ... */</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre></div><p>这里要加过渡的话，就是酱紫：</p> <div class="language-html extra-class"><pre class="language-html"><code><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>transition</span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>component-fade<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>component</span> <span class="token attr-name"><span class="token namespace">v-bind:</span>is</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>currentView<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>component</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>transition</span><span class="token punctuation">&gt;</span></span>
</code></pre></div><p>路由组件的过渡，大概也是属于这种方式。</p> <h3 id="_6-4-3-过渡模式"><a href="#_6-4-3-过渡模式" class="header-anchor">#</a> 6.4.3 过渡模式</h3> <p>多元素和多组件的过渡中，会有上一个的离开以及下一个的进入的情况，于是便有了这个过渡模式。Vue 提供了以下过渡模式：</p> <ul><li><strong>in-out</strong>：新元素先进行过渡，完成之后当前元素过渡离开。</li> <li><strong>out-in</strong>：当前元素先进行过渡，完成之后新元素过渡进入。</li></ul> <div class="language-html extra-class"><pre class="language-html"><code><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>transition</span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>fade<span class="token punctuation">&quot;</span></span> <span class="token attr-name">mode</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>out-in<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>
  <span class="token comment">&lt;!-- ... the buttons ... --&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>transition</span><span class="token punctuation">&gt;</span></span>
</code></pre></div><p>当然，在不做任何设置的时候，transition 的默认行为是：进入和离开同时发生。</p> <h3 id="_6-4-4-实现逻辑"><a href="#_6-4-4-实现逻辑" class="header-anchor">#</a> 6.4.4 实现逻辑</h3> <p>其实多元素和组件的过渡，大致总体是跟单个元素的过渡相似的，大概是多了切换的过程，也就是一个结束另外一个开始的过渡把。所以这里可以看看以下的源码：</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token comment">// replace old child transition data with fresh one</span>
<span class="token comment">// important for dynamic transitions!</span>
<span class="token keyword">const</span> <span class="token literal-property property">oldData</span><span class="token operator">:</span> Object <span class="token operator">=</span>
  oldChild <span class="token operator">&amp;&amp;</span> <span class="token punctuation">(</span>oldChild<span class="token punctuation">.</span>data<span class="token punctuation">.</span>transition <span class="token operator">=</span> <span class="token function">extend</span><span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">,</span> data<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// handle transition mode</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>mode <span class="token operator">===</span> <span class="token string">&quot;out-in&quot;</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token comment">// return placeholder node and queue update when leave finishes</span>
  <span class="token keyword">this</span><span class="token punctuation">.</span>_leaving <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
  <span class="token function">mergeVNodeHook</span><span class="token punctuation">(</span>oldData<span class="token punctuation">,</span> <span class="token string">&quot;afterLeave&quot;</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>_leaving <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">$forceUpdate</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">return</span> <span class="token function">placeholder</span><span class="token punctuation">(</span>h<span class="token punctuation">,</span> rawChild<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>mode <span class="token operator">===</span> <span class="token string">&quot;in-out&quot;</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">isAsyncPlaceholder</span><span class="token punctuation">(</span>child<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> oldRawChild<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token keyword">let</span> delayedLeave<span class="token punctuation">;</span>
  <span class="token keyword">const</span> <span class="token function-variable function">performLeave</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token function">delayedLeave</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">;</span>
  <span class="token function">mergeVNodeHook</span><span class="token punctuation">(</span>data<span class="token punctuation">,</span> <span class="token string">&quot;afterEnter&quot;</span><span class="token punctuation">,</span> performLeave<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token function">mergeVNodeHook</span><span class="token punctuation">(</span>data<span class="token punctuation">,</span> <span class="token string">&quot;enterCancelled&quot;</span><span class="token punctuation">,</span> performLeave<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token function">mergeVNodeHook</span><span class="token punctuation">(</span>oldData<span class="token punctuation">,</span> <span class="token string">&quot;delayLeave&quot;</span><span class="token punctuation">,</span> <span class="token parameter">leave</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    delayedLeave <span class="token operator">=</span> leave<span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre></div><p>这里大概的操作是：<br>
(1) 当 mode 为'out-in'时，将下一个元素的插入时间延后，等待上一个元素的离开动画完成后，再触发下一个元素的插入和进入动画。<br>
(2) 当 mode 为'in-out'时，将上一个元素的移除时间延后，等待下一个元素的进入动画完成后，再触发上一个元素的移除和离开动画。</p> <p>因为前面我们也分析过，进入动画和元素插入捆绑，而离开动画和元素移除捆绑，所以这里只需要控制元素的延时变更，就能得到动画控制的效果了。</p> <h2 id="_6-5-flip-与列表过渡"><a href="#_6-5-flip-与列表过渡" class="header-anchor">#</a> 6.5 FLIP 与列表过渡</h2> <p>关于 FLIP，在了解之后会有“这世界上竟然还有这种骚操作”的感觉。</p> <h3 id="_6-5-1-什么是-flip"><a href="#_6-5-1-什么是-flip" class="header-anchor">#</a> 6.5.1 什么是 FLIP</h3> <p>FLIP 代表 First、Last、Invert、Play，见表 6-4：</p> <p>表 6-4 FLIP 说明</p> <table><thead><tr><th>字母代表</th> <th>说明</th></tr></thead> <tbody><tr><td>First</td> <td>元素参与 transtion 的初始状态</td></tr> <tr><td>Last</td> <td>元素的最终状态</td></tr> <tr><td>Invert</td> <td>清楚了元素从开始到结束是如何改变的，如它的 width、height、opacity。下一步，你应用 transform 和 opacity 的变化来扭转或反转它们。如果元素已经向下移动到初始和结束状态之间的 90px，然后在 Y 轴上应用 transform -90px。这让元素看起来仍然在初始的位置，所以元素并没有达到最终的位置</td></tr> <tr><td>Play</td> <td>为你之前改变的属性开启过渡，然后移除反转的改变。因为当移除 transform 和 opacity 时，元素都在它们的最终位置。这会缓解从伪造的初始位置到最终位置的计算量</td></tr></tbody></table> <h3 id="_6-5-2-如何理解-flip"><a href="#_6-5-2-如何理解-flip" class="header-anchor">#</a> 6.5.2 如何理解 FLIP</h3> <p>最初看到这些解释的时候，也是不是很明白在说啥。后面仔细看了下，真的是太厉害了。这里用我自己的方式来讲一下。</p> <h4 id="动画的性能"><a href="#动画的性能" class="header-anchor">#</a> 动画的性能</h4> <p>故事要从很多年前说起，当年 CSS3 还没普遍兼容，我们使用着最原始的 jQuery 来做动画。那时候怎么做的呢？通常是通过定时器和直接改变元素的 style 来实现动画的，jQuery 的话，一般就是用<code>.animate()</code>来做的。</p> <p>但是这样做会有什么问题呢？这要从浏览器渲染说起，这里描述下两个概念：</p> <ul><li><strong>Repaint</strong>：屏幕的一部分要重画，比如某个 CSS 的背景色变了。但是元素的几何尺寸没有变。</li> <li><strong>Reflow</strong>：意味着元件的几何尺寸变了，我们需要重新验证并计算 Render Tree。是 Render Tree 的一部分或全部发生了变化。</li></ul> <p>简单说就是，Reflow 的成本比 Repaint 的成本高得多的多。而我们手动设置这些动画的时候，则可能会中了死穴。但是不是所有属性动画消耗的性能都一样，其中消耗最低的是 transform 和 opacity 两个属性（当然还有会触发 Composite 的其他 CSS 属性），其次是 Paint 相关属性。所以从某种角度来说，我们使用 CSS3 的 transform 这些属性，是可以优化动画性能的。</p> <h4 id="元素的样式规整"><a href="#元素的样式规整" class="header-anchor">#</a> 元素的样式规整</h4> <p>更多的情况是，我们在计算动画时，通常很难计算最终状态，例如一些元素的重新排列，而我们又希望能有个过渡动画的时候。</p> <p>通常来说，我们如果真的需要完整地计算整个样式过渡的动画，很多时候只能将元素设置<code>position: absolute/fixed</code>这样实现，但是这样存在很多性能、兼容和响应式问题，不好维护同时也不够灵活。</p> <h4 id="flip-过渡"><a href="#flip-过渡" class="header-anchor">#</a> FLIP 过渡</h4> <p>FLIP，本质上它是一个准则，而不是一个框架或库。这是对动画的一种思考，试图在浏览器中能更轻易地让动画达到 60 fps（关于后者，这里先不做过多讨论哈）。</p> <p><strong>FLIP 将动画翻转</strong>，可以这样理解：<br>
(1) 正常动画过渡，会计算最终 B 状态，再减去初始 A 状态，然后根据想要的过渡时间，定时器设置每段时间的变化，应用到元素上。<br>
(2) FLIP 的做法是，A 状态到 B 状态，不用手动计算每一帧的改变，而是计算从 B 到 A 的反向动画，然后下一帧直接切换 B 状态，在把反向动画应用在 B 上。</p> <p>其实真实元素是直接从 A-&gt;B，但是由于在 B 状态上加了反向动画，所以用户看起来像是过渡。还是看不懂？来看看代码：</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token comment">// 获取初始位置A</span>
<span class="token keyword">var</span> first <span class="token operator">=</span> el<span class="token punctuation">.</span><span class="token function">getBoundingClientRect</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">// 将其移动到最终位置B</span>
el<span class="token punctuation">.</span>classList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">&quot;totes-at-the-end&quot;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">// 获取最终位置B</span>
<span class="token keyword">var</span> last <span class="token operator">=</span> el<span class="token punctuation">.</span><span class="token function">getBoundingClientRect</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">// 翻转，计算B-&gt;A需要的移动</span>
<span class="token keyword">var</span> invert <span class="token operator">=</span> first<span class="token punctuation">.</span>top <span class="token operator">-</span> last<span class="token punctuation">.</span>top<span class="token punctuation">;</span>

<span class="token comment">// 从翻转的位置到最终位置</span>
<span class="token keyword">var</span> player <span class="token operator">=</span> el<span class="token punctuation">.</span><span class="token function">animate</span><span class="token punctuation">(</span>
  <span class="token punctuation">[</span>
    <span class="token punctuation">{</span> <span class="token literal-property property">transform</span><span class="token operator">:</span> <span class="token string">&quot;translateY(&quot;</span> <span class="token operator">+</span> invert <span class="token operator">+</span> <span class="token string">&quot;px)&quot;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">{</span> <span class="token literal-property property">transform</span><span class="token operator">:</span> <span class="token string">&quot;translateY(0)&quot;</span> <span class="token punctuation">}</span>
  <span class="token punctuation">]</span><span class="token punctuation">,</span>
  <span class="token punctuation">{</span>
    <span class="token literal-property property">duration</span><span class="token operator">:</span> <span class="token number">300</span><span class="token punctuation">,</span>
    <span class="token literal-property property">easing</span><span class="token operator">:</span> <span class="token string">&quot;cubic-bezier(0,0,0.32,1)&quot;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">// 动画结束后做一些处理，如移除样式什么的。</span>
</code></pre></div><h3 id="_6-5-3-列表过渡"><a href="#_6-5-3-列表过渡" class="header-anchor">#</a> 6.5.3 列表过渡</h3> <h4 id="transition-group-组件"><a href="#transition-group-组件" class="header-anchor">#</a> transition-group 组件</h4> <p>前面我们讲到 transition 组件，多半用于单个节点，或者是同一时间中只渲染单个节点的情况。</p> <p>这里我们要渲染多个节点的过渡效果，需要用到 transition-group 组件：</p> <ul><li>不同于 transition（transition 不会转换为真实元素），它会以一个真实元素呈现：默认为一个 <code>&lt;span&gt;</code>。你也可以通过 tag 特性更换为其他元素</li> <li>内部元素总是需要提供唯一的 key 属性值（transition 是当有相同标签名的元素切换时，需要通过 key 特性设置唯一的值来标记）</li></ul> <h4 id="demo"><a href="#demo" class="header-anchor">#</a> demo</h4> <p>我们来看个简单的例子：</p> <div class="language-html extra-class"><pre class="language-html"><code><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>list-demo<span class="token punctuation">&quot;</span></span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>demo<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name"><span class="token namespace">v-on:</span>click</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>add<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>Add<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name"><span class="token namespace">v-on:</span>click</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>remove<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>Remove<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>transition-group</span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>list<span class="token punctuation">&quot;</span></span> <span class="token attr-name">tag</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>p<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">v-for</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>item in items<span class="token punctuation">&quot;</span></span> <span class="token attr-name"><span class="token namespace">v-bind:</span>key</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>item<span class="token punctuation">&quot;</span></span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>list-item<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>
      {{ item }}
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>transition-group</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
</code></pre></div><p>我们每次随机添加两个、删除两个：</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">new</span> <span class="token class-name">Vue</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  <span class="token literal-property property">el</span><span class="token operator">:</span> <span class="token string">&quot;#list-demo&quot;</span><span class="token punctuation">,</span>
  <span class="token literal-property property">data</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token literal-property property">items</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">,</span> <span class="token number">6</span><span class="token punctuation">,</span> <span class="token number">7</span><span class="token punctuation">,</span> <span class="token number">8</span><span class="token punctuation">,</span> <span class="token number">9</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
    <span class="token literal-property property">nextNum</span><span class="token operator">:</span> <span class="token number">10</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token literal-property property">methods</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token function-variable function">randomIndex</span><span class="token operator">:</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">return</span> Math<span class="token punctuation">.</span><span class="token function">floor</span><span class="token punctuation">(</span>Math<span class="token punctuation">.</span><span class="token function">random</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token keyword">this</span><span class="token punctuation">.</span>items<span class="token punctuation">.</span>length<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token function-variable function">add</span><span class="token operator">:</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">this</span><span class="token punctuation">.</span>items<span class="token punctuation">.</span><span class="token function">splice</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">randomIndex</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>nextNum<span class="token operator">++</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token keyword">this</span><span class="token punctuation">.</span>items<span class="token punctuation">.</span><span class="token function">splice</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">randomIndex</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>nextNum<span class="token operator">++</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token function-variable function">remove</span><span class="token operator">:</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">this</span><span class="token punctuation">.</span>items<span class="token punctuation">.</span><span class="token function">splice</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">randomIndex</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token keyword">this</span><span class="token punctuation">.</span>items<span class="token punctuation">.</span><span class="token function">splice</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">randomIndex</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre></div><div class="language-CSS extra-class"><pre class="language-css"><code><span class="token selector">.list-item</span> <span class="token punctuation">{</span>
  <span class="token property">display</span><span class="token punctuation">:</span> inline-block<span class="token punctuation">;</span>
  <span class="token property">margin-right</span><span class="token punctuation">:</span> 10px<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token selector">.list-enter-active, .list-leave-active</span> <span class="token punctuation">{</span>
  <span class="token property">transition</span><span class="token punctuation">:</span> all 1s<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
.list-enter<span class="token punctuation">,</span> .list-leave-to
<span class="token comment">/* .list-leave-active for below version 2.1.8 */</span> <span class="token punctuation">{</span>
  <span class="token property">opacity</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span>
  <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">translateY</span><span class="token punctuation">(</span>30px<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre></div><blockquote><p><a href="https://vue-eboook-1255459943.cos.ap-chengdu.myqcloud.com/6/3-transition-group/3-transition-group.html" target="_blank" rel="noopener noreferrer">点击此处查看页面效果<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a> <a href="https://github.com/godbasin/vue-ebook/tree/vue-sourcecode/6/3-transition-group" target="_blank" rel="noopener noreferrer">点击此处查看源码<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></p></blockquote> <p>这里我们能看到，只有新加和删除的元素才会有过渡效果，其他的元素会瞬间移动，没有过渡效果。为了解决这个问题，我们需要用到 FLIP。</p> <h3 id="_6-5-4-排序过渡"><a href="#_6-5-4-排序过渡" class="header-anchor">#</a> 6.5.4 排序过渡</h3> <p>什么是排序过渡？大概是：<br>
(1) 新增的元素有进入动画。<br>
(2) 移除的元素有离开动画。<br>
(3) 位置移动的元素有移动动画。</p> <p>前面两点我们已经可以通过之前的方式实现了，关于第三点，我们使用 FLIP。比较关键的，我们看看 Vue 中的实现代码：</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token comment">// we divide the work into three loops to avoid mixing DOM reads and writes</span>
<span class="token comment">// in each iteration - which helps prevent layout thrashing.</span>
children<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span>callPendingCbs<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 1. 之前如果有没结束的过渡，先结束掉</span>
children<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span>recordPosition<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 2. 记录新的位置</span>
children<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span>applyTranslation<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 3. 计算反向动画，生成个CSS动画</span>

<span class="token comment">// force reflow to put everything in position</span>
<span class="token keyword">const</span> <span class="token literal-property property">body</span><span class="token operator">:</span> any <span class="token operator">=</span> document<span class="token punctuation">.</span>body<span class="token punctuation">;</span>
<span class="token keyword">const</span> <span class="token literal-property property">f</span><span class="token operator">:</span> number <span class="token operator">=</span> body<span class="token punctuation">.</span>offsetHeight<span class="token punctuation">;</span> <span class="token comment">// eslint-disable-line</span>

children<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter"><span class="token literal-property property">c</span><span class="token operator">:</span> VNode</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span>c<span class="token punctuation">.</span>data<span class="token punctuation">.</span>moved<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">var</span> <span class="token literal-property property">el</span><span class="token operator">:</span> any <span class="token operator">=</span> c<span class="token punctuation">.</span>elm<span class="token punctuation">;</span>
    <span class="token keyword">var</span> <span class="token literal-property property">s</span><span class="token operator">:</span> any <span class="token operator">=</span> el<span class="token punctuation">.</span>style<span class="token punctuation">;</span>
    <span class="token comment">// 添加反向CSS动画</span>
    <span class="token function">addTransitionClass</span><span class="token punctuation">(</span>el<span class="token punctuation">,</span> moveClass<span class="token punctuation">)</span><span class="token punctuation">;</span>
    s<span class="token punctuation">.</span>transform <span class="token operator">=</span> s<span class="token punctuation">.</span>WebkitTransform <span class="token operator">=</span> s<span class="token punctuation">.</span>transitionDuration <span class="token operator">=</span> <span class="token string">&quot;&quot;</span><span class="token punctuation">;</span>
    <span class="token comment">// 监听动画结束，结束之后要移除CSS动画类名啦</span>
    el<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span>
      transitionEndEvent<span class="token punctuation">,</span>
      <span class="token punctuation">(</span>el<span class="token punctuation">.</span><span class="token function-variable function">_moveCb</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token function">cb</span><span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>e <span class="token operator">||</span> <span class="token regex"><span class="token regex-delimiter">/</span><span class="token regex-source language-regex">transform$</span><span class="token regex-delimiter">/</span></span><span class="token punctuation">.</span><span class="token function">test</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>propertyName<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
          el<span class="token punctuation">.</span><span class="token function">removeEventListener</span><span class="token punctuation">(</span>transitionEndEvent<span class="token punctuation">,</span> cb<span class="token punctuation">)</span><span class="token punctuation">;</span>
          el<span class="token punctuation">.</span>_moveCb <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
          <span class="token function">removeTransitionClass</span><span class="token punctuation">(</span>el<span class="token punctuation">,</span> moveClass<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
      <span class="token punctuation">}</span><span class="token punctuation">)</span>
    <span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre></div><p>这里还有个反向动画的计算可能大家会稍微感兴趣：</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">function</span> <span class="token function">applyTranslation</span><span class="token punctuation">(</span><span class="token parameter"><span class="token literal-property property">c</span><span class="token operator">:</span> VNode</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> oldPos <span class="token operator">=</span> c<span class="token punctuation">.</span>data<span class="token punctuation">.</span>pos<span class="token punctuation">;</span>
  <span class="token keyword">const</span> newPos <span class="token operator">=</span> c<span class="token punctuation">.</span>data<span class="token punctuation">.</span>newPos<span class="token punctuation">;</span>
  <span class="token comment">// 计算原位置和最终位置位移</span>
  <span class="token keyword">const</span> dx <span class="token operator">=</span> oldPos<span class="token punctuation">.</span>left <span class="token operator">-</span> newPos<span class="token punctuation">.</span>left<span class="token punctuation">;</span>
  <span class="token keyword">const</span> dy <span class="token operator">=</span> oldPos<span class="token punctuation">.</span>top <span class="token operator">-</span> newPos<span class="token punctuation">.</span>top<span class="token punctuation">;</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span>dx <span class="token operator">||</span> dy<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    c<span class="token punctuation">.</span>data<span class="token punctuation">.</span>moved <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> s <span class="token operator">=</span> c<span class="token punctuation">.</span>elm<span class="token punctuation">.</span>style<span class="token punctuation">;</span>
    <span class="token comment">// 设置反位移</span>
    s<span class="token punctuation">.</span>transform <span class="token operator">=</span> s<span class="token punctuation">.</span>WebkitTransform <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">translate(</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>dx<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">px,</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>dy<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">px)</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>
    s<span class="token punctuation">.</span>transitionDuration <span class="token operator">=</span> <span class="token string">&quot;0s&quot;</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre></div><p>其实也不难，跟上面的 FLIP demo 逻辑相似。排序过渡中，官方文档提到一个<code>v-move</code>特性，它会在元素的改变定位的过程中应用。像前面的类名一样，可以通过 name 属性来自定义前缀，也可以通过<code>move-class</code>属性手动设置，像上面的。</p> <div class="language-CSS extra-class"><pre class="language-css"><code><span class="token selector">.list-move</span> <span class="token punctuation">{</span>
  <span class="token property">transition</span><span class="token punctuation">:</span> transform 1s<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre></div><blockquote><p><a href="https://vue-eboook-1255459943.cos.ap-chengdu.myqcloud.com/6/4-transition-group-flip/4-transition-group-flip.html" target="_blank" rel="noopener noreferrer">点击此处查看页面效果<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a> <a href="https://github.com/godbasin/vue-ebook/tree/vue-sourcecode/6/4-transition-group-flip" target="_blank" rel="noopener noreferrer">点击此处查看源码<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></p></blockquote> <p>但其实这只是一个设置 transition 的过渡时间和过渡曲线而已，真正的亮点是藏在 FLIP 里面。</p> <h3 id="_6-5-5-列表交错过渡"><a href="#_6-5-5-列表交错过渡" class="header-anchor">#</a> 6.5.5 列表交错过渡</h3> <p>交错过渡听起来好厉害的样子，其实说白了就是：设置延时，给元素设置顺序延时的动画效果，通过 data 属性与 JavaScript 通信就可以实现啦。大概就是获取元素的序号，计算延时，然后设置定时器动画。我们来实现一个延时动画效果的下拉菜单：</p> <div class="language-html extra-class"><pre class="language-html"><code><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>template</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</span></span>
    <span class="token comment">&lt;!-- 这是基于 bootstrap 常见的下拉菜单样式 --&gt;</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>row<span class="token punctuation">&quot;</span></span> <span class="token special-attr"><span class="token attr-name">style</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span><span class="token value css language-css"><span class="token property">margin-left</span><span class="token punctuation">:</span> 20px<span class="token punctuation">;</span></span><span class="token punctuation">&quot;</span></span></span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>mr5<span class="token punctuation">&quot;</span></span> <span class="token special-attr"><span class="token attr-name">style</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span><span class="token value css language-css"><span class="token property">font-size</span><span class="token punctuation">:</span> 14px<span class="token punctuation">;</span></span><span class="token punctuation">&quot;</span></span></span><span class="token punctuation">&gt;</span></span>下拉菜单<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">&gt;</span></span>
      <span class="token comment">&lt;!-- v-click-outside 绑定方法名 --&gt;</span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>btn-group<span class="token punctuation">&quot;</span></span> <span class="token attr-name">v-click-outside</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>closeMenu<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>
        <span class="token comment">&lt;!-- 这里点击会切换菜单是否可见 --&gt;</span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span>
          <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>button<span class="token punctuation">&quot;</span></span>
          <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>btn btn-default dropdown-toggle<span class="token punctuation">&quot;</span></span>
          <span class="token attr-name">@click</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>isMenuShown = !isMenuShown<span class="token punctuation">&quot;</span></span>
        <span class="token punctuation">&gt;</span></span>
          点击 <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>caret<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>transition-group</span>
          <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>dropdown-menu<span class="token punctuation">&quot;</span></span>
          <span class="token special-attr"><span class="token attr-name">style</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span><span class="token value css language-css"><span class="token property">display</span><span class="token punctuation">:</span>block<span class="token punctuation">;</span></span><span class="token punctuation">&quot;</span></span></span>
          <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>staggered-fade<span class="token punctuation">&quot;</span></span>
          <span class="token attr-name">tag</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>ul<span class="token punctuation">&quot;</span></span>
          <span class="token attr-name">:css</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>false<span class="token punctuation">&quot;</span></span>
          <span class="token attr-name"><span class="token namespace">v-on:</span>before-enter</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>beforeEnter<span class="token punctuation">&quot;</span></span>
          <span class="token attr-name"><span class="token namespace">v-on:</span>enter</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>enter<span class="token punctuation">&quot;</span></span>
          <span class="token attr-name"><span class="token namespace">v-on:</span>leave</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>leave<span class="token punctuation">&quot;</span></span>
        <span class="token punctuation">&gt;</span></span>
          <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span>
            <span class="token attr-name">v-for</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>(item, index) in itemsShow<span class="token punctuation">&quot;</span></span>
            <span class="token attr-name">:key</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>item<span class="token punctuation">&quot;</span></span>
            <span class="token attr-name">:data-index</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>index<span class="token punctuation">&quot;</span></span>
          <span class="token punctuation">&gt;</span></span>
            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span><span class="token punctuation">&gt;</span></span>{{item}}<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">&gt;</span></span>
          <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>transition-group</span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>template</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">&gt;</span></span><span class="token script"><span class="token language-javascript">
  <span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token punctuation">{</span>
    <span class="token literal-property property">el</span><span class="token operator">:</span> <span class="token string">&quot;#app&quot;</span><span class="token punctuation">,</span>
    <span class="token literal-property property">data</span><span class="token operator">:</span> <span class="token punctuation">{</span>
      <span class="token literal-property property">items</span><span class="token operator">:</span> <span class="token punctuation">[</span>
        <span class="token string">&quot;Action&quot;</span><span class="token punctuation">,</span>
        <span class="token string">&quot;Another action&quot;</span><span class="token punctuation">,</span>
        <span class="token string">&quot;Something else here&quot;</span><span class="token punctuation">,</span>
        <span class="token string">&quot;Separated link&quot;</span>
      <span class="token punctuation">]</span><span class="token punctuation">,</span>
      <span class="token literal-property property">isMenuShown</span><span class="token operator">:</span> <span class="token boolean">false</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token literal-property property">computed</span><span class="token operator">:</span> <span class="token punctuation">{</span>
      <span class="token function">itemsShow</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>items<span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span><span class="token parameter">x</span> <span class="token operator">=&gt;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>isMenuShown<span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token literal-property property">methods</span><span class="token operator">:</span> <span class="token punctuation">{</span>
      <span class="token function-variable function">beforeEnter</span><span class="token operator">:</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">el</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        el<span class="token punctuation">.</span>style<span class="token punctuation">.</span>opacity <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>
        el<span class="token punctuation">.</span>style<span class="token punctuation">.</span>height <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token function-variable function">enter</span><span class="token operator">:</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">el<span class="token punctuation">,</span> done</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token comment">// 看看这里的定时器延时</span>
        <span class="token keyword">var</span> delay <span class="token operator">=</span> el<span class="token punctuation">.</span>dataset<span class="token punctuation">.</span>index <span class="token operator">*</span> <span class="token number">800</span><span class="token punctuation">;</span>
        <span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
          <span class="token function">Velocity</span><span class="token punctuation">(</span>el<span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">opacity</span><span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token literal-property property">height</span><span class="token operator">:</span> <span class="token string">&quot;1.6em&quot;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">complete</span><span class="token operator">:</span> done <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span><span class="token punctuation">,</span> delay<span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token function-variable function">leave</span><span class="token operator">:</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">el<span class="token punctuation">,</span> done</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token comment">// 还有这里的定时器延时</span>
        <span class="token keyword">var</span> delay <span class="token operator">=</span> el<span class="token punctuation">.</span>dataset<span class="token punctuation">.</span>index <span class="token operator">*</span> <span class="token number">800</span><span class="token punctuation">;</span>
        <span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
          <span class="token function">Velocity</span><span class="token punctuation">(</span>el<span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">opacity</span><span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token literal-property property">height</span><span class="token operator">:</span> <span class="token number">0</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">complete</span><span class="token operator">:</span> done <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span><span class="token punctuation">,</span> delay<span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span><span class="token punctuation">;</span>
</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span>
</code></pre></div><blockquote><p><a href="https://vue-eboook-1255459943.cos.ap-chengdu.myqcloud.com/6/5-staggered-dragdown/5-staggered-dragdown.html" target="_blank" rel="noopener noreferrer">点击此处查看页面效果<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a> <a href="https://github.com/godbasin/vue-ebook/tree/vue-sourcecode/6/5-staggered-dragdown" target="_blank" rel="noopener noreferrer">点击此处查看源码<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></p></blockquote> <p>Vue 动画设计得真心好用，配合 CSS3 原有的一些动画能力，我们能用很简单的方法来实现一些比较复杂和酷炫的过渡效果。虽然很多时候动画都是比较受忽视的地方，但就跟 PPT 过渡一样，一些简单的过渡效果和动画，能让受众前后连贯起来，是一种很舒服的体验，也常常是让人眼前一亮的东西。</p></div> <!----> <footer class="page-edit"><div class="edit-link"><a href="https://github.com/godbasin/vue-ebook/edit/ebook-sourcecode/docs/vue-ebook/6.md" target="_blank" rel="noopener noreferrer">帮阿猪改善此页面！</a> <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></div> <!----> <div style="margin-top:30px;"><div class="el-row" style="margin-left:-10px;margin-right:-10px;"><div class="el-col el-col-24 el-col-sm-0 el-col-md-2 el-col-lg-4" style="padding-left:10px;padding-right:10px;display:block;"><div style="width:1px;height:1px;"></div></div> <div class="el-col el-col-24 el-col-sm-24 el-col-md-18 el-col-lg-16" style="padding-left:10px;padding-right:10px;"><div class="el-card box-card is-always-shadow"><div class="el-card__header"><div class="clearfix"><span>温馨提示喵</span></div></div><div class="el-card__body"> <div class="el-row" style="margin-left:-10px;margin-right:-10px;"><div class="el-col el-col-24 el-col-xs-24 el-col-sm-12" style="padding-left:10px;padding-right:10px;"><div class="el-image"><div class="image-slot"><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/assets/img/loading.gif" style="width:100%;"></div><!----></div></div> <div class="el-col el-col-24 el-col-xs-24 el-col-sm-12" style="padding-left:10px;padding-right:10px;"><div class="copyright-text"><div>本书采用“保持署名—非商用”<a href="http://creativecommons.org/licenses/by-nc/4.0/" target="_blank">创意共享4.0许可证</a>。只要保持原作者署名和非商用，您可以自由地阅读、分享、修改本书。</div> <div>作者：<a href="https://github.com/godbasin" target="_blank">被删</a></div></div></div></div></div></div></div></div></div></footer> <div class="page-nav"><p class="inner"><span class="prev">
        ←
        <a href="/vue-ebook/vue-ebook/5.html" class="prev">
          第5章 常用指令和自定义指令
        </a></span> <span class="next"><a href="/vue-ebook/vue-ebook/7.html">
          第7章 Vue Router 路由搭建应用
        </a>
        →
      </span></p></div>  <div class="gitalk-container theme-default-content"><div id="gitalk-container" class="content"></div></div></main> <div id="kitty-container"><span><div role="tooltip" id="el-popover-4467" aria-hidden="true" class="el-popover el-popper" style="width:undefinedpx;display:none;"><!----><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/2code2.jpg" class="image"> <div class="text">牡羊猪的猫粮罐</div> </div><span class="el-popover__reference-wrapper"><div id="kitty" style="background:url(https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/assets/img/kitty1.svg);"></div></span></span> <div class="el-dialog__wrapper" style="display:none;"><div role="dialog" aria-modal="true" aria-label="牡羊猪是这样渐渐胖成猪的喵（点击图片可以切换噢）" class="el-dialog" style="margin-top:15vh;"><div class="el-dialog__header"><span class="el-dialog__title">牡羊猪是这样渐渐胖成猪的喵（点击图片可以切换噢）</span><button type="button" aria-label="Close" class="el-dialog__headerbtn"><i class="el-dialog__close el-icon el-icon-close"></i></button></div><!----><!----></div></div></div></div><div class="global-ui"></div></div>
    <script src="/vue-ebook/assets/js/app.a856ee15.js" defer></script><script src="/vue-ebook/assets/js/2.2a4d101b.js" defer></script><script src="/vue-ebook/assets/js/3.9083e877.js" defer></script><script src="/vue-ebook/assets/js/20.f187c6c2.js" defer></script>
  </body>
</html>
